﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using SoundEdge.Media.Wave;
using SoundEdge.Media.Settings;
using System.ComponentModel.Composition;
using System.IO;
using SoundEdge.Media.Wave.SampleProviders;
using SoundEdge.Common;

namespace SoundEdge.Media
{
    public delegate void DeviceStatusChangeEventHandler(object sender, DeviceStatusChangeEventArgs e);
    public delegate void MessageEventHandler(object sender, MessageEventArgs e);
    public delegate void TimeElapsedEventHandler(object sender, TimeElapsedEventArgs e);

    [Export]
    public partial class AudioPlayer : Component
    {
        private IWavePlayer _waveOut;
        private WaveStream _fileWaveStream;
        private Action<float> _setVolumeDelegate;
        private PlayerState _state;
        public PlayerState State { get { return _state; } }

        public WaveStream Stream { get { return _fileWaveStream; } }
        
        public IWaveProvider Provider { get; protected set; }
        
        private System.Timers.Timer _timer;

        public event DeviceStatusChangeEventHandler DeviceStatusChange;
        public event MessageEventHandler Message;
        public event EventHandler<StreamVolumeEventArgs> PreVolumeMetter;
        public event EventHandler<StreamVolumeEventArgs> PostVolumeMetter;
        public event TimeElapsedEventHandler TimeElapsed;
        public event EventHandler PositionChange;
        public event EventHandler<StreamMetterEventArgs> StreamMetter;

        [ImportMany(typeof(IInputFileFormatPlugin))]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public IEnumerable<IInputFileFormatPlugin> InputFileFormats { get; set; }

        private IOutputDevicePlugin _outputPlugin;
        public IOutputDevicePlugin OutputPlugin { get { return _outputPlugin; } set { _outputPlugin = value; OnDeviceStatusChange(PlayerState.Ready); } }

        private string _fileName;
        public string FileName { get { return _fileName; } set { _fileName = value; Init(); } }

        public AudioPlayer()
        {
            InitializeComponent();
            InitializePlayer();
        }

        void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (_waveOut != null && _fileWaveStream != null)
            {
                TimeSpan currentTime = (_waveOut.PlaybackState == PlaybackState.Stopped) ? TimeSpan.Zero : _fileWaveStream.CurrentTime;
                if (_fileWaveStream.Position >= _fileWaveStream.Length)
                {
                    Stop();
                }
                OnTimeElapsed(_fileWaveStream.TotalTime, _fileWaveStream.CurrentTime);
            }
        }

        public virtual void SetPosition(int seconds)
        {
            if (_waveOut != null)
            {
                _fileWaveStream.CurrentTime = TimeSpan.FromSeconds(seconds);
            }
        }

        protected virtual void OnTimeElapsed(TimeSpan totalTime, TimeSpan currentTime)
        {
            TimeElapsedEventHandler handler = TimeElapsed;
            if (handler != null)
            {
                TimeElapsedEventArgs args = new TimeElapsedEventArgs(totalTime, currentTime);
                foreach (TimeElapsedEventHandler singleCast in handler.GetInvocationList())
                {
                    ISynchronizeInvoke syncInvoke = singleCast.Target as ISynchronizeInvoke;
                    try
                    {
                        if (syncInvoke != null && syncInvoke.InvokeRequired)
                        {
                            syncInvoke.Invoke(singleCast, new object[] { this, args });
                        }
                        else
                        {
                            singleCast(this, args);
                        }
                    }
                    catch
                    { }
                }
            }
        }

        public AudioPlayer(IContainer container)
        {
            container.Add(this);
            InitializeComponent();
            InitializePlayer();
        }

        private void InitializePlayer()
        {
            MEFUtils.GetAssemblyContainer().ComposeParts(this);
            _timer = new System.Timers.Timer(500);
            _timer.Elapsed += new System.Timers.ElapsedEventHandler(_timer_Elapsed);
        }

        public virtual void OnDeviceStatusChange(PlayerState state)
        {
            if (_state < PlayerState.StreamReady)
            {
                _state = _outputPlugin != null && _outputPlugin.IsAvailable && _outputPlugin.Settings != null ? PlayerState.Ready : PlayerState.NA;
            }
            else
            {
                _state = state;
            }

            DeviceStatusChangeEventHandler handler = DeviceStatusChange;
            if (handler != null)
            {
                DeviceStatusChangeEventArgs args = new DeviceStatusChangeEventArgs(_state, _fileWaveStream != null ? _fileWaveStream.TotalTime : TimeSpan.Zero);
                foreach (DeviceStatusChangeEventHandler singleCast in handler.GetInvocationList())
                {
                    ISynchronizeInvoke syncInvoke = singleCast.Target as ISynchronizeInvoke;
                    try
                    {
                        if (syncInvoke != null && syncInvoke.InvokeRequired)
                        {
                            syncInvoke.Invoke(singleCast, new object[] { this, args });
                        }
                        else
                        {
                            singleCast(this, args);
                        }
                    }
                    catch
                    { }
                }
            }
        }

        public virtual void OnMessage(string message, MessageType messageType)
        {
            MessageEventHandler handler = Message;
            if (handler != null)
            {
                MessageEventArgs args = new MessageEventArgs(message, messageType);
                foreach (MessageEventHandler singleCast in handler.GetInvocationList())
                {
                    ISynchronizeInvoke syncInvoke = singleCast.Target as ISynchronizeInvoke;
                    try
                    {
                        if (syncInvoke != null && syncInvoke.InvokeRequired)
                        {
                            syncInvoke.Invoke(singleCast, new object[] { this, args });
                        }
                        else
                        {
                            singleCast(this, args);
                        }
                    }
                    catch
                    { }
                }
            }
        }

        protected virtual void OnError(string errorMessage, string friendlyMessage)
        {
#if DEBUG
            OnMessage(errorMessage, MessageType.Error);
#else
                OnMessage(friendlyMessage, MessageType.Error);
#endif
        }

        protected virtual void OnError(Exception ex, string friendlyMessage)
        {
            OnError(ex.GetBaseException().Message, friendlyMessage);
        }

        public virtual void Play()
        {
            try
            {
                if (_state != PlayerState.StreamReady)
                {
                    Init();
                }

                if (_state != PlayerState.StreamReady)
                {
                    throw new Exception("Media not ready!");
                }

                if (_waveOut != null)
                {
                    if (_waveOut.PlaybackState == PlaybackState.Playing)
                    {
                        return;
                    }
                    else if (_waveOut.PlaybackState == PlaybackState.Paused)
                    {
                        _waveOut.Play();
                        //groupBoxDriverModel.Enabled = false;
                        return;
                    }
                }

                _timer.Start();
                _waveOut.Play();
                OnDeviceStatusChange(PlayerState.Playing);
            }
            catch (Exception ex)
            {
                OnError(ex, "An error occured while trying to play the file!");
            }
        }

        protected virtual void Init()
        {
            try
            {
                if (_state == PlayerState.Ready &&
                    File.Exists(_fileName) &&
                    InputFileFormats != null &&
                    InputFileFormats.FirstOrDefault(item => item.Extension.ToLower() == Path.GetExtension(_fileName)) != null)
                {
                    _state = PlayerState.StreamReady;
                }

                if (_state < PlayerState.StreamReady)
                {
                    throw new Exception("Media not ready!");
                }

                try
                {
                    CreateWaveOut();
                }
                catch (Exception driverCreateException)
                {
                    OnError(string.Format("{0}", driverCreateException.Message), "Error creating driver!");
                    return;
                }

                ISampleProvider sampleProvider = null;
                try
                {
                    sampleProvider = CreateInputStream(_fileName);
                }
                catch (Exception createException)
                {
                    OnError(string.Format("{0}", createException.Message), "Error Loading File");
                    return;
                }

                try
                {
                    if (StreamMetter != null)
                    {
                        _waveOut.StreamMetter += OnStreamMetter;
                    }
                    Provider = new SampleToWaveProvider(sampleProvider);
                    _waveOut.Init(Provider);
                }
                catch (Exception initException)
                {
                    OnError(string.Format("{0}", initException.Message), "Error Initializing Output");
                    return;
                }

                //_setVolumeDelegate(volumeSlider1.Volume);
                OnDeviceStatusChange(PlayerState.StreamReady);
                OnTimeElapsed(_fileWaveStream.TotalTime, _fileWaveStream.CurrentTime);
            }
            catch (Exception ex)
            {
                OnError(ex, "An error occured while initializing the file!");
            }
        }

        public virtual void Stop()
        {
            if (_waveOut != null)
            {
                _waveOut.Stop();
                _timer.Stop();
                SetPosition(0);
                OnTimeElapsed(_fileWaveStream.TotalTime, _fileWaveStream.CurrentTime);
                OnDeviceStatusChange(PlayerState.Stopped);
            }
        }

        public virtual void Pause()
        {
            if (_waveOut != null)
            {
                if (_waveOut.PlaybackState == PlaybackState.Playing)
                {
                    _waveOut.Pause();
                    OnDeviceStatusChange(PlayerState.Paused);
                }
                else if (_waveOut.PlaybackState == PlaybackState.Paused)
                {
                    _waveOut.Resume();
                    OnDeviceStatusChange(PlayerState.Playing);
                }
            }
        }

        private void CreateWaveOut()
        {
            CloseWaveOut();
            _waveOut = _outputPlugin.CreateDevice();
        }

        private void CloseWaveOut()
        {
            if (_waveOut != null)
            {
                _waveOut.Stop();
            }
            if (_fileWaveStream != null)
            {
                // this one really closes the file and ACM conversion
                _fileWaveStream.Dispose();
                this._setVolumeDelegate = null;
            }
            if (_waveOut != null)
            {
                _waveOut.Dispose();
                _waveOut = null;
            }
        }

        private IInputFileFormatPlugin GetPluginForFile(string fileName)
        {
            return (from f in this.InputFileFormats where fileName.EndsWith(f.Extension, StringComparison.OrdinalIgnoreCase) select f).FirstOrDefault();
        }

        protected virtual void OnPreVolumeMeter(object sender, StreamVolumeEventArgs e)
        {
            EventHandler<StreamVolumeEventArgs> handler = PreVolumeMetter;
            if (handler != null)
            {
                foreach (EventHandler<StreamVolumeEventArgs> singleCast in handler.GetInvocationList())
                {
                    ISynchronizeInvoke syncInvoke = singleCast.Target as ISynchronizeInvoke;
                    try
                    {
                        if (syncInvoke != null && syncInvoke.InvokeRequired)
                        {
                            syncInvoke.Invoke(singleCast, new object[] { this, e });
                        }
                        else
                        {
                            singleCast(this, e);
                        }
                    }
                    catch
                    { }
                }
            }

            // we know it is stereo
            //waveformPainter1.AddMax(e.MaxSampleValues[0]);
            //waveformPainter2.AddMax(e.MaxSampleValues[1]);
        }

        protected virtual void OnPostVolumeMeter(object sender, StreamVolumeEventArgs e)
        {
            EventHandler<StreamVolumeEventArgs> handler = PostVolumeMetter;
            if (handler != null)
            {
                foreach (EventHandler<StreamVolumeEventArgs> singleCast in handler.GetInvocationList())
                {
                    ISynchronizeInvoke syncInvoke = singleCast.Target as ISynchronizeInvoke;
                    try
                    {
                        if (syncInvoke != null && syncInvoke.InvokeRequired)
                        {
                            syncInvoke.Invoke(singleCast, new object[] { this, e });
                        }
                        else
                        {
                            singleCast(this, e);
                        }
                    }
                    catch
                    { }
                }
            }

            // we know it is stereo
            //volumeMeter1.Amplitude = e.MaxSampleValues[0];
            //volumeMeter2.Amplitude = e.MaxSampleValues[1];
        }

        private ISampleProvider CreateInputStream(string fileName)
        {
            var plugin = GetPluginForFile(fileName);
            if (plugin == null)
            {
                throw new InvalidOperationException("Unsupported file extension");
            }
            _fileWaveStream = plugin.CreateWaveStream(fileName);
            _fileWaveStream.PositionChange += OnPositionChange;
            var waveChannel = new SampleChannel(_fileWaveStream);
            _setVolumeDelegate = (vol) => waveChannel.Volume = vol;
            waveChannel.PreVolumeMeter += OnPreVolumeMeter;

            var postVolumeMeter = new MeteringSampleProvider(waveChannel);
            postVolumeMeter.StreamVolume += OnPostVolumeMeter;

            return postVolumeMeter;
        }

        protected virtual void OnStreamMetter(object sender, StreamMetterEventArgs e)
        {
            EventHandler<StreamMetterEventArgs> handler = StreamMetter;
            if (handler == null)
            {
                return;
            }
            foreach (EventHandler<StreamMetterEventArgs> singleCast in handler.GetInvocationList())
            {
                ISynchronizeInvoke syncInvoke = singleCast.Target as ISynchronizeInvoke;
                try
                {
                    if (syncInvoke != null && syncInvoke.InvokeRequired)
                    {
                        syncInvoke.Invoke(singleCast, new object[] { this, e });
                    }
                    else
                    {
                        singleCast(this, e);
                    }
                }
                catch
                { }
            }
        }

        //protected virtual void OnSample(object sender, SampleEventArgs e)
        //{
        //    EventHandler<SampleEventArgs> handler = Sample;
        //    if (handler == null)
        //    {
        //        return;
        //    }
        //    foreach (EventHandler<SampleEventArgs> singleCast in handler.GetInvocationList())
        //    {
        //        ISynchronizeInvoke syncInvoke = singleCast.Target as ISynchronizeInvoke;
        //        try
        //        {
        //            if (syncInvoke != null && syncInvoke.InvokeRequired)
        //            {
        //                syncInvoke.Invoke(singleCast, new object[] { this, e });
        //            }
        //            else
        //            {
        //                singleCast(this, e);
        //            }
        //        }
        //        catch
        //        { }
        //    }
        //}

        protected virtual void OnPositionChange(object sender, EventArgs e)
        {
            EventHandler handler = PositionChange;
            if (handler == null)
            {
                return;
            }
            foreach (EventHandler singleCast in handler.GetInvocationList())
            {
                ISynchronizeInvoke syncInvoke = singleCast.Target as ISynchronizeInvoke;
                try
                {
                    if (syncInvoke != null && syncInvoke.InvokeRequired)
                    {
                        syncInvoke.Invoke(singleCast, new object[] { this, EventArgs.Empty });
                    }
                    else
                    {
                        singleCast(this, EventArgs.Empty);
                    }
                }
                catch
                { }
            }
        }
    }
}
